// Copyright 2018 The gooid Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.

/*
 * Copyright (C) 2016 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

/**
 * @addtogroup Media
 * @{
 */

/**
 * @file NdkImageReader.h
 */

/*
 * This file defines an NDK API.
 * Do not remove methods.
 * Do not change method signatures.
 * Do not change the value of constants.
 * Do not change the size of any of the classes defined in here.
 * Do not reference types that are not part of the NDK.
 * Do not #include files that aren't part of the NDK.
 */

package media

/*
#include "media/NdkImageReader.h"

extern void cgoImageListenerCallback(void*, AImageReader*);
*/
import "C"

import (
	"unsafe"

	app "github.com/gooid/gooid/internal/ndk"
)

/**
 * AImage is an opaque type that allows direct application access to image data rendered into a
 * {@link ANativeWindow}.
 */
type ImageReader C.AImageReader

func (reader *ImageReader) cptr() *C.AImageReader {
	return (*C.AImageReader)(reader)
}

/**
 * Create a new reader for images of the desired size and format.
 *
 * <p>
 * The maxImages parameter determines the maximum number of {@link AImage} objects that can be
 * acquired from the {@link AImageReader} simultaneously. Requesting more buffers will use up
 * more memory, so it is important to use only the minimum number necessary for the use case.
 * </p>
 * <p>
 * The valid sizes and formats depend on the source of the image data.
 * </p>
 *
 * @param width The default width in pixels of the Images that this reader will produce.
 * @param height The default height in pixels of the Images that this reader will produce.
 * @param format The format of the Image that this reader will produce. This must be one of the
 *            AIMAGE_FORMAT_* enum value defined in {@link AIMAGE_FORMATS}. Note that not all
 *            formats are supported. One example is {@link AIMAGE_FORMAT_PRIVATE}, as it is not
 *            intended to be read by applications directly. That format is supported by
 *            {@link AImageReader_newWithUsage} introduced in API 26.
 * @param maxImages The maximum number of images the user will want to access simultaneously. This
 *            should be as small as possible to limit memory use. Once maxImages Images are obtained
 *            by the user, one of them has to be released before a new {@link AImage} will become
 *            available for access through {@link AImageReader_acquireLatestImage} or
 *            {@link AImageReader_acquireNextImage}. Must be greater than 0.
 * @param reader The created image reader will be filled here if the method call succeeeds.
 *
 * @return <ul>
 *         <li>{@link AMEDIA_OK} if the method call succeeds.</li>
 *         <li>{@link AMEDIA_ERROR_INVALID_PARAMETER} if reader is NULL, or one or more of width,
 *                 height, format, maxImages arguments is not supported.</li>
 *         <li>{@link AMEDIA_ERROR_UNKNOWN} if the method fails for some other reasons.</li></ul>
 *
 * @see AImage
 */
//media_status_t AImageReader_new(
//        int32_t width, int32_t height, int32_t format, int32_t maxImages,
//        /*out*/AImageReader** reader) __INTRODUCED_IN(24);
func NewImageReader(width, height int, format Formats, maxImages int) (*ImageReader, error) {
	var reader *C.AImageReader
	ret := Status(C.AImageReader_new(C.int32_t(width), C.int32_t(height),
		C.int32_t(format), C.int32_t(maxImages), &reader))
	if ret == nil {
		return (*ImageReader)(reader), ret
	}
	return nil, ret
}

/**
 * Delete an {@link AImageReader} and return all images generated by this reader to system.
 *
 * <p>This method will return all {@link AImage} objects acquired by this reader (via
 * {@link AImageReader_acquireNextImage} or {@link AImageReader_acquireLatestImage}) to system,
 * making any of data pointers obtained from {@link AImage_getPlaneData} invalid. Do NOT access
 * the reader object or any of those data pointers after this method returns.</p>
 *
 * @param reader The image reader to be deleted.
 */
//void AImageReader_delete(AImageReader* reader) __INTRODUCED_IN(24);
func (reader *ImageReader) Delete() {
	delete(listenerCallbackMap, unsafe.Pointer(reader.cptr()))
	C.AImageReader_delete(reader.cptr())
}

/**
 * Get a {@link ANativeWindow} that can be used to produce {@link AImage} for this image reader.
 *
 * @param reader The image reader of interest.
 * @param window The output {@link ANativeWindow} will be filled here if the method call succeeds.
 *                The {@link ANativeWindow} is managed by this image reader. Do NOT call
 *                {@link ANativeWindow_release} on it. Instead, use {@link AImageReader_delete}.
 *
 * @return <ul>
 *         <li>{@link AMEDIA_OK} if the method call succeeds.</li>
 *         <li>{@link AMEDIA_ERROR_INVALID_PARAMETER} if reader or window is NULL.</li></ul>
 */
//media_status_t AImageReader_getWindow(AImageReader* reader, /*out*/ANativeWindow** window) __INTRODUCED_IN(24);
func (reader *ImageReader) GetWindow() (*app.Window, error) {
	var win *C.ANativeWindow
	ret := Status(C.AImageReader_getWindow(reader.cptr(), &win))
	if ret == nil {
		return (*app.Window)(win), ret
	}
	return nil, ret
}

/**
 * Query the default width of the {@link AImage} generated by this reader, in pixels.
 *
 * <p>The width may be overridden by the producer sending buffers to this reader's
 * {@link ANativeWindow}. If so, the actual width of the images can be found using
 * {@link AImage_getWidth}.</p>
 *
 * @param reader The image reader of interest.
 * @param width the default width of the reader will be filled here if the method call succeeeds.
 *
 * @return <ul>
 *         <li>{@link AMEDIA_OK} if the method call succeeds.</li>
 *         <li>{@link AMEDIA_ERROR_INVALID_PARAMETER} if reader or width is NULL.</li></ul>
 */
//media_status_t AImageReader_getWidth(const AImageReader* reader, /*out*/int32_t* width) __INTRODUCED_IN(24);
func (reader *ImageReader) GetWidth() (int, error) {
	var width C.int32_t
	ret := Status(C.AImageReader_getWidth(reader.cptr(), &width))
	return int(width), ret
}

/**
 * Query the default height of the {@link AImage} generated by this reader, in pixels.
 *
 * <p>The height may be overridden by the producer sending buffers to this reader's
 * {@link ANativeWindow}. If so, the actual height of the images can be found using
 * {@link AImage_getHeight}.</p>
 *
 * @param reader The image reader of interest.
 * @param height the default height of the reader will be filled here if the method call succeeeds.
 *
 * @return <ul>
 *         <li>{@link AMEDIA_OK} if the method call succeeds.</li>
 *         <li>{@link AMEDIA_ERROR_INVALID_PARAMETER} if reader or height is NULL.</li></ul>
 */
//media_status_t AImageReader_getHeight(const AImageReader* reader, /*out*/int32_t* height) __INTRODUCED_IN(24);
func (reader *ImageReader) GetHeight() (int, error) {
	var height C.int32_t
	ret := Status(C.AImageReader_getHeight(reader.cptr(), &height))
	return int(height), ret
}

/**
 * Query the format of the {@link  AImage} generated by this reader.
 *
 * @param reader The image reader of interest.
 * @param format the fromat of the reader will be filled here if the method call succeeeds. The
 *                value will be one of the AIMAGE_FORMAT_* enum value defiend in {@link NdkImage.h}.
 *
 * @return <ul>
 *         <li>{@link AMEDIA_OK} if the method call succeeds.</li>
 *         <li>{@link AMEDIA_ERROR_INVALID_PARAMETER} if reader or format is NULL.</li></ul>
 */
//media_status_t AImageReader_getFormat(const AImageReader* reader, /*out*/int32_t* format) __INTRODUCED_IN(24);
func (reader *ImageReader) GetFormat() (Formats, error) {
	var format C.int32_t
	ret := Status(C.AImageReader_getFormat(reader.cptr(), &format))
	return Formats(format), ret
}

/**
 * Query the maximum number of concurrently acquired {@link AImage}s of this reader.
 *
 * @param reader The image reader of interest.
 * @param maxImages the maximum number of concurrently acquired images of the reader will be filled
 *                here if the method call succeeeds.
 *
 * @return <ul>
 *         <li>{@link AMEDIA_OK} if the method call succeeds.</li>
 *         <li>{@link AMEDIA_ERROR_INVALID_PARAMETER} if reader or maxImages is NULL.</li></ul>
 */
//media_status_t AImageReader_getMaxImages(const AImageReader* reader, /*out*/int32_t* maxImages) __INTRODUCED_IN(24);
func (reader *ImageReader) GetMaxImages() (int, error) {
	var maxImages C.int32_t
	ret := Status(C.AImageReader_getMaxImages(reader.cptr(), &maxImages))
	return int(maxImages), ret
}

/**
 * Acquire the next {@link AImage} from the image reader's queue.
 *
 * <p>Warning: Consider using {@link AImageReader_acquireLatestImage} instead, as it will
 * automatically release older images, and allow slower-running processing routines to catch
 * up to the newest frame. Usage of {@link AImageReader_acquireNextImage} is recommended for
 * batch/background processing. Incorrectly using this method can cause images to appear
 * with an ever-increasing delay, followed by a complete stall where no new images seem to appear.
 * </p>
 *
 * <p>
 * This method will fail if {@link AImageReader_getMaxImages maxImages} have been acquired with
 * {@link AImageReader_acquireNextImage} or {@link AImageReader_acquireLatestImage}. In particular
 * a sequence of {@link AImageReader_acquireNextImage} or {@link AImageReader_acquireLatestImage}
 * calls greater than {@link AImageReader_getMaxImages maxImages} without calling
 * {@link AImage_delete} in-between will exhaust the underlying queue. At such a time,
 * {@link AMEDIA_IMGREADER_MAX_IMAGES_ACQUIRED} will be returned until more images are released with
 * {@link AImage_delete}.
 * </p>
 *
 * @param reader The image reader of interest.
 * @param image the acquired {@link AImage} will be filled here if the method call succeeeds.
 *
 * @return <ul>
 *         <li>{@link AMEDIA_OK} if the method call succeeds.</li>
 *         <li>{@link AMEDIA_ERROR_INVALID_PARAMETER} if reader or image is NULL.</li>
 *         <li>{@link AMEDIA_IMGREADER_MAX_IMAGES_ACQUIRED} if the number of concurrently acquired
 *                 images has reached the limit.</li>
 *         <li>{@link AMEDIA_IMGREADER_NO_BUFFER_AVAILABLE} if there is no buffers currently
 *                 available in the reader queue.</li>
 *         <li>{@link AMEDIA_ERROR_UNKNOWN} if the method fails for some other reasons.</li></ul>
 *
 * @see AImageReader_acquireLatestImage
 */
//media_status_t AImageReader_acquireNextImage(AImageReader* reader, /*out*/AImage** image) __INTRODUCED_IN(24);
func (reader *ImageReader) AcquireNextImage() (*Image, error) {
	var image *C.AImage
	ret := Status(C.AImageReader_acquireNextImage(reader.cptr(), &image))
	return (*Image)(image), ret
}

/**

 * Acquire the latest {@link AImage} from the image reader's queue, dropping older images.
 *
 * <p>
 * This operation will acquire all the images possible from the image reader, but
 * {@link AImage_delete} all images that aren't the latest. This function is recommended to use over
 * {@link AImageReader_acquireNextImage} for most use-cases, as it's more suited for real-time
 * processing.
 * </p>
 * <p>
 * Note that {@link AImageReader_getMaxImages maxImages} should be at least 2 for
 * {@link AImageReader_acquireLatestImage} to be any different than
 * {@link AImageReader_acquireNextImage} - discarding all-but-the-newest {@link AImage} requires
 * temporarily acquiring two {@link AImage}s at once. Or more generally, calling
 * {@link AImageReader_acquireLatestImage} with less than two images of margin, that is
 * (maxImages - currentAcquiredImages < 2) will not discard as expected.
 * </p>
 * <p>
 * This method will fail if {@link AImageReader_getMaxImages maxImages} have been acquired with
 * {@link AImageReader_acquireNextImage} or {@link AImageReader_acquireLatestImage}. In particular
 * a sequence of {@link AImageReader_acquireNextImage} or {@link AImageReader_acquireLatestImage}
 * calls greater than {@link AImageReader_getMaxImages maxImages} without calling
 * {@link AImage_delete} in-between will exhaust the underlying queue. At such a time,
 * {@link AMEDIA_IMGREADER_MAX_IMAGES_ACQUIRED} will be returned until more images are released with
 * {@link AImage_delete}.
 * </p>
 *
 * @param reader The image reader of interest.
 * @param image the acquired {@link AImage} will be filled here if the method call succeeeds.
 *
 * @return <ul>
 *         <li>{@link AMEDIA_OK} if the method call succeeds.</li>
 *         <li>{@link AMEDIA_ERROR_INVALID_PARAMETER} if reader or image is NULL.</li>
 *         <li>{@link AMEDIA_IMGREADER_MAX_IMAGES_ACQUIRED} if the number of concurrently acquired
 *                 images has reached the limit.</li>
 *         <li>{@link AMEDIA_IMGREADER_NO_BUFFER_AVAILABLE} if there is no buffers currently
 *                 available in the reader queue.</li>
 *         <li>{@link AMEDIA_ERROR_UNKNOWN} if the method fails for some other reasons.</li></ul>
 *
 * @see AImageReader_acquireNextImage
 */
//media_status_t AImageReader_acquireLatestImage(AImageReader* reader, /*out*/AImage** image) __INTRODUCED_IN(24);
func (reader *ImageReader) AcquireLatestImage() (*Image, error) {
	var image *C.AImage
	ret := Status(C.AImageReader_acquireLatestImage(reader.cptr(), &image))
	return (*Image)(image), ret
}

/**
 * Signature of the callback which is called when a new image is available from {@link AImageReader}.
 *
 * @param context The optional application context provided by user in
 *                {@link AImageReader_setImageListener}.
 * @param session The camera capture session whose state is changing.
 */
//typedef void (*AImageReader_ImageCallback)(void* context, AImageReader* reader);

//typedef struct AImageReader_ImageListener {
/// Optional application context passed as the first parameter of the callback.
//    void*                      context;

/**
 * This callback is called when there is a new image available in the image reader's queue.
 *
 * <p>The callback happens on one dedicated thread per {@link AImageReader} instance. It is okay
 * to use AImageReader_* and AImage_* methods within the callback. Note that it is possible that
 * calling {@link AImageReader_acquireNextImage} or {@link AImageReader_acquireLatestImage}
 * returns {@link AMEDIA_IMGREADER_NO_BUFFER_AVAILABLE} within this callback. For example, when
 * there are multiple images and callbacks queued, if application called
 * {@link AImageReader_acquireLatestImage}, some images will be returned to system before their
 * corresponding callback is executed.</p>
 */
//    AImageReader_ImageCallback onImageAvailable;
//} AImageReader_ImageListener;

/**
 * Set the onImageAvailable listener of this image reader.
 *
 * Calling this method will replace previously registered listeners.
 *
 * @param reader The image reader of interest.
 * @param listener The {@link AImageReader_ImageListener} to be registered. Set this to NULL if
 *                 the application no longer needs to listen to new images.
 *
 * @return <ul>
 *         <li>{@link AMEDIA_OK} if the method call succeeds.</li>
 *         <li>{@link AMEDIA_ERROR_INVALID_PARAMETER} if reader is NULL.</li></ul>
 */
//media_status_t AImageReader_setImageListener(
//        AImageReader* reader, AImageReader_ImageListener* listener) __INTRODUCED_IN(24);
func (reader *ImageReader) SetImageListener(onImageAvailable func(*ImageReader)) error {
	var listener C.AImageReader_ImageListener
	listener.context = unsafe.Pointer(reader.cptr())
	listener.onImageAvailable = (C.AImageReader_ImageCallback)(C.cgoImageListenerCallback)
	listenerCallbackMap[listener.context] = onImageAvailable
	return Status(C.AImageReader_setImageListener(reader.cptr(), &listener))
}

var listenerCallbackMap = map[unsafe.Pointer]func(*ImageReader){}

//export cgoImageListenerCallback
func cgoImageListenerCallback(context unsafe.Pointer, reader *C.AImageReader) {
	creader := (*C.AImageReader)(context)
	if fn, ok := listenerCallbackMap[context]; ok {
		fn((*ImageReader)(creader))
	}
}
